<template>
  <div>

    <div style="position: fixed;right: 0;bottom: 0;margin: 20px;z-index: 5000">
      <transition name="el-zoom-in-bottom">
        <div class="box-chat" v-show="showChat">
          <div class="box-header">
            <img  src="@/assets/images/bot.webp" alt="">
            <h4>AI 对话</h4>
            <i class="el-icon-close cursor-pointer" @click="showChat = false"/>
          </div>
          <div class="box-body">
            <div class="talkShow" >
              <div class="bot-talk" v-show="isShowHelp">
                <div class="bot-talk-problem">
                  <span>您好，我是您的小助手，您可以向我提出问题。</span>
                  <a v-for="i in problem" @click="handleProblem(i)" :key="i"><i class="el-icon-edit"></i> {{ i }}</a>
                </div>
              </div>


              <div :class="item.person === 'bot'?'bot-talk':'mineTalk'" v-for="(item,index) in talkList" :key="index">
                <div v-if="item.person === 'bot'">
                  <MdPreview  :markdown-text="item.say"/>
                  <div style="margin-right: 10px;height: 30px;width: 100%" v-show="item.isEnd">
                    <i class="el-icon-document-copy icon" @click="copyTextToClipboard(item.say)"></i>
                  </div>
                </div>
                <div v-else>
                  <p v-text="item.say"></p>
                </div>
              </div>
            </div>
          </div>


          <div class="box-footer">
            <form @submit.prevent="getQuestion">
              <el-input class="search-input" v-model="problemText" placeholder="请输入您的问题">
                <i slot="suffix" class="el-input__icon el-icon-position" style="cursor: pointer;"
                   @click="getQuestion"></i>
              </el-input>
            </form>

          </div>
        </div>
      </transition>
    </div>


    <!--AI图标-->
    <img style="height: 80px;position: fixed;right: 0;bottom: 50px;margin: 20px;z-index: 5000"
         v-drag-chat="{click:clickHandler}" class="cursor-pointer"
         v-if="!showChat" src="@/assets/images/bot.webp" alt="">
  </div>
</template>

<script>
import MdPreview from "./MdPreview.vue";

export default {
  name: 'ChatEmbed',
  components: {
    MdPreview
  },
  directives: {
    dragChat: {
      bind(el, binding) {
        const oDiv = el // 当前元素
        // 导致用户不能选择文本
        // document.onselectstart = () => {
        //   return false
        // }
        oDiv.onmousedown = e => {
          const elWidth = oDiv.offsetWidth
          const elHeight = oDiv.offsetHeight


          // 计算当前元素距离可视区的距离
          const marginTop = oDiv.style.marginTop ? parseInt(oDiv.style.marginTop) : 0
          const marginRight = oDiv.style.marginRight ? parseInt(oDiv.style.marginRight) : 0
          const disX = e.clientX - oDiv.offsetLeft - marginRight
          const disY = e.clientY - oDiv.offsetTop + marginTop

          let isMove = false
          document.onmousemove = e => {
            console.log("移动事件")
            isMove = true
            // 通过事件委托，计算移动的距离
            // 因为浏览器里并不能直接取到并且使用clientX、clientY,所以使用事件委托在内部做完赋值
            const l = e.clientX - disX
            // 计算元素距离视口右边距
            const r = window.innerWidth - l - elWidth
            const t = e.clientY - disY
            // 计算移动当前元素的位置，并且给该元素样式中的right和top值赋值
            // 这里用right定位，因为小组件的关闭按钮再右上方，避免隐藏后按钮移位
            if (r > 0 - marginRight && r + elWidth < window.innerWidth - marginRight) {
              oDiv.style.right = r + 'px'
            }
            if (t > 0 - marginTop && t + elHeight < window.innerHeight - marginTop) {
              oDiv.style.top = t + 'px'
            }
          }
          document.onmouseup = () => { // 鼠标抬起，清空之前所在的位置，新拖拽的位置已生成并赋值
            document.onmousemove = null
            document.onmouseup = null

            // 触发点击回调函数
            !isMove && binding.value.click && binding.value.click();
          }
          // return false不加的话可能导致黏连，拖到一个地方时div粘在鼠标上不下来，相当于onmouseup失效
          return false
        }
      }
    }
  },
  data() {
    return {
      showChat: false,
      isShowHelp: true,
      talkList: [
        // {id: "1", person: 'bot', say: '你好，有什么可以帮到你呢？'},
      ],
      problem: [
        "如何通过机械比能监测低效事件？",
        "分步测试的具体步骤？",
        "摩阻测试的步骤和应用？",
      ],
      problemText: "",
      reader: null,
      isReader: null,
    }
  },
  mounted(){
  },
  updated() {
    this.scrollToBottom();
  },
  methods: {

    scrollToBottom() {
      this.$nextTick(() => {
        let box = this.$el.querySelector(".talkShow");
        let maxScroll = box.scrollHeight - box.clientHeight;
        // 对话时一直滚动
        box.scrollTop = maxScroll;
        // 滑动到最下面滚动
        // let bottom = maxScroll - box.scrollTop;
        // if (bottom <= 100) {
        //   box.scrollTop = maxScroll;
        // }
      })
    },
    getQuestion() {
      if (this.problemText === '') {
        console.log('请输入内容');
        return;
      }
      let box = this.$el.querySelector(".talkShow");
      box.scrollTop = box.scrollHeight;
      // 中止之前的对话
      this.abortChat()
      // admin提问数据push()
      this.talkList.push({id: Date.now(), person: 'admin', say: this.problemText});
      this.getAnswer({message:this.problemText,model:"ollama"});
      // 清空输入栏数据
      this.problemText = '';
    },
    // 调用API获取回答， 模拟后端返回的数据流式输出
    async getAnswer(params) {
      const url = 'http://localhost:1006/chat/chat_message?message='+ params.message

      let headers = new Headers();
      headers.append("Accept", "*/*");
      headers.append("Connection", "keep-alive");

      let responsePromise = fetch(url, {
        method: 'GET',
        headers: headers,
        redirect: 'follow'
      });

      responsePromise.then(response => {
        const reader = response.body.getReader();
        this.reader = reader;
        this.isReader = true;

        // 流式输出文字逐个展示
        let text = "";

        // bot 回答
        let answer = {id: Date.now(), person: 'bot', say: '...', isEnd: false}
        this.talkList.push(answer);

        let tempResult = ''
        /**
         *
         * @param done  是否结束
         * @param value 值
         */
        const write_stream = ({done, value}) => {
          try {
            if (done) {
              // 是否读取结束
              answer.isEnd = true
              this.isReader = false;
              return
            }
            const decoder = new TextDecoder('utf-8')
            let str = decoder.decode(value, {stream: true})
            // 因为数据流返回流并不是按照后端chunk返回 我们希望得到的chunk是data:{xxx}\n\n 但是它获取到的可能是 data:{ -> xxx}\n\n 总而言之就是 fetch不能保证每个chunk都说以data:开始 \n\n结束
            tempResult += str
            const split = tempResult.match(/data:.*}\n\n/g)
            if (split) {
              str = split.join('')
              tempResult = tempResult.replace(str, '')
            } else {
              return reader.read().then(write_stream)
            }

            if (str && str.startsWith('data:')) {
              if (split) {
                for (const index in split) {
                  const chunk = JSON?.parse(split[index].replace('data:', ''))
                  text += chunk.content;
                  answer.say = text;

                  // if (chunk.is_end) {
                  //   // 流处理成功 返回成功回调
                  //   return Promise.resolve()
                  // }
                }
              }
            }
          } catch (e) {
            return Promise.reject(e)
          }
          return reader.read().then(write_stream)
        }

        // 读取流
        reader.read().then(write_stream);
      });
    },
    // 中止对话
    abortChat(){
      if (this.isReader){
        // 主动取消流
        this.reader.cancel('用户主动关闭流').then(() => {
          console.log('流已主动关闭');
          this.reader.releaseLock();
        });
      }
    },
    // 处理问题点击
    handleProblem(problem) {
      this.problemText = problem
      this.getQuestion()
    },
    clickHandler() {
      this.showChat = true
    },
    // 复制文本
    copyTextToClipboard(text) {
      this.talkList = []
      navigator.clipboard.writeText(text)
          .then(() => {
            console.log('文本已成功复制到剪贴板')
          })
          .catch(err => {
            console.error('复制失败：', err);
          });
    }
  }
}
</script>

<style lang="scss" scoped>
.box-chat {
  width: 448px;
  background-color: rgb(245, 246, 247);
  border-radius: 5px;
  color: rgb(31, 35, 41);

  .box-header {
    height: 56px;
    margin-bottom: 20px;
    border-bottom: 1px solid #dee0e3;

    img {
      height: 56px;
      float: left;
      margin: 0 5px;
    }

    h4 {
      font-size: 20px;
      font-weight: 700;
      color: rgb(31, 35, 41);
      float: left;
      line-height: 56px;
      margin: 0;
    }

    i {
      float: right;
      font-size: 20px;
      margin: 0 10px;
      line-height: 56px;
    }
  }

  .box-body {
    height: 448px;
    padding: 4px;
    overflow: hidden;

    .talkShow {
      width: 100%;
      height: 100%;
      overflow-x: hidden;
      overflow-y: scroll;

      .bot-talk {
        margin: 10px;

        .icon{
          padding: 3px;
          font-size: 18px;
          float: right;

          &:hover {
            border-radius: 3px;
            background-color: #d1d1d3;
            cursor: pointer;
          }
        }

        > div {
          background: white;
          width: 100%;
          padding: 5px 10px;
          border: 1px solid rgb(214, 216, 219);
          border-radius: 0 10px 10px 10px;
          box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, .12);
        }

        .bot-talk-problem {
          span {
            font-size: 14px;
          }

          a {
            display: block;
            cursor: pointer;
            text-decoration: none;
            background: rgb(245, 246, 247);
            padding: 6px 10px;
            margin: 12px;

            i {
              color: #3370ff;
            }
          }
        }
      }


      .mineTalk {
        margin: 10px;
        text-align: right;

        div {
          display: inline-block;
          border-radius: 10px 0px 10px 10px;
          background: #409eff;
          color: #fff;
          padding: 5px 10px;
          word-break: break-all;
          text-align: left;
        }
      }
    }
  }

  .box-footer {
    padding: 10px;
    box-shadow: 0px 2px 4px 5px rgba(31, 35, 41, .12);

    .search-input {
      border: 1px solid #dee0e3;
      border-radius: 5px;

      ::v-deep .el-input__inner {
        color: #1f2329 !important;
      }

    }
  }
}

</style>